#include <kern/sched.h>
#include <kern/spinlock.h>
#include <kern/proc.h>
#include <kern/debug.h>
#include <pool.h>

/* global vars */
struct spinlock_t sched_lock;
struct queue_t q1;
struct queue_t q2;
struct queue_t* activeq;
struct queue_t* expireq;

extern void swtch(struct context_t **oldctx, struct context_t* newctx);

static void swap(void** a, void** b)
{
	void* tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}

#define SWAP(_type, _a, _b) {\
	_type tmp;	\
	tmp = (_a);	\
	(_a) = (_b);	\
	(_b) = tmp;	\
}

static struct proc_t* get_next_runnable(void)
{
	struct proc_t* next;
	//acquire(&sched_lock);
	if (!(next = queue_dequeue(activeq))) {
		SWAP(struct queue_t*, activeq, expireq);
		monitor_write("chaning active and expired queues\n");
		next = queue_dequeue(activeq);
	}
	//release(&sched_lock);
	return next;
}

void sched_init(void)
{
	initlock(&sched_lock);
	queue_init(&q1, offsetof(struct proc_t, link));
	queue_init(&q2, offsetof(struct proc_t, link));

	activeq = &q1;
	expireq = &q2;
}

void scheduler(void)
{
	struct proc_t* next;

	while (1) {
		next = get_next_runnable();
		ASSERT(next != NULL, "could not find next proc to run");
		// chaning the currproc must be done b4 we swtch the stacks otherwise
		// next will not be accessable
		// TODO: push currproc back to runq if needed
		cpu->currproc = next;
		ASSERT(cpu->currproc->state == PROC_RUNNABLE,
			"scheduling a non runnable pro for run");

		cpu->currproc->state = PROC_RUNNING;
	
		//vm_swtch_uvm(cpulocal->currproc);
		monitor_write("about to run next proc: ");
		//monitor_write_hex(cpu->currproc->id);
		monitor_write_hex(cpu->currproc->kctx->eip);
		monitor_write("\n");
		// switch kernel stacks and return to sched
		swtch(&cpu->schedctx, cpu->currproc->kctx);
		// this is the entry point of sched
		// for now, we dont have a running process
		cpu->currproc == NULL;
	}
}

void sched(void)
{
	// TODO: clear interrupts on this cpu so schedctx will not be lost
	// save interrupt enabled
	ASSERT(cpu->currproc->state != PROC_RUNNING, "sched invoked with proc running");
	swtch(&cpu->currproc->kctx, cpu->schedctx);
	// restore interrupt enabled
	// TODO: enable interrupts
}

int sched_set_runnable(struct proc_t* p)
{
	ASSERT(p->state != PROC_RUNNABLE, "proc is already runnable");
	p->state = PROC_RUNNABLE;
	//acquire(&sched_lock);
	queue_enqueue(expireq, p);
	//release(&sched_lock);

	return 0;
}

